package io.kiki.stack.http.feign.SpringMvcContract.support;

import io.kiki.stack.http.feign.codec.EncodeException;
import io.kiki.stack.http.feign.form.multipart.AbstractWriter;
import io.kiki.stack.http.feign.form.multipart.Output;
import io.kiki.stack.http.feign.form.util.PojoUtil;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.function.Predicate;

import static io.kiki.stack.http.feign.form.ContentProcessor.CRLF;


public abstract class AbstractFormWriter extends AbstractWriter {

    @Override
    public boolean isApplicable(Object object) {
        return !isTypeOrCollection(object, o -> o instanceof MultipartFile) && isTypeOrCollection(object, PojoUtil::isUserPojo);
    }

    @Override
    public void write(Output output, String key, Object object) throws EncodeException {
        try {
            String string = "Content-Disposition: form-data; name=\"" + key + '"' + CRLF + "Content-Type: " + getContentType() + "; charset=" + output.getCharset().name() + CRLF + CRLF + writeAsString(object);

            output.write(string);
        } catch (IOException e) {
            throw new EncodeException(e.getMessage());
        }
    }

    protected abstract MediaType getContentType();

    protected abstract String writeAsString(Object object) throws IOException;

    private boolean isTypeOrCollection(Object object, Predicate<Object> isType) {
        if (object == null) {
            return false;
        }
        if (object.getClass().isArray()) {
            int len = Array.getLength(object);
            if (len > 0) {
                Object one = Array.get(object, 0);
                return len > 1 && one != null && isType.test(one);
            }
            return false;
        } else
            if (object instanceof Iterable<?> iterable) {
                Iterator<?> iterator = iterable.iterator();

                return iterator.hasNext() && isType.test(iterator.next());
            } else {
                return isType.test(object);
            }
    }

}
